package net.fxug.hokuriku.chocolat.view
{
    import flash.desktop.NativeApplication;
    import flash.display.NativeWindow;
    import flash.display.Screen;
    import flash.errors.IllegalOperationError;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.TimerEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.utils.Timer;

    /**
     * 通知ウィンドウの管理クラス
     *
     * @see http://www.adobe.com/devnet/air/flex/quickstart/creating_toast-style_windows.html
     * @author shoito
     */
    public class NotificationManager extends EventDispatcher
    {
        private static var instance:NotificationManager;

        /** 「へぇ」 ボタンによるリアクションを示す */
        public static const NOTIFY_GOOD:String = "good";

        /** 「ぶぅ」ボタンによるリアクションを示す */
        public static const NOTIFY_BAD:String = "bad";

//        /** コメントによるリアクションを示す */
//        public static const NOTIFY_COMMENT:String = "comment";

        /** 通知ウィンドウの表示残時間カウントイベント */
        public static const LIFE_TICK_EVENT:String = "lifeTick";

        /** 通知ウィンドウの表示時間(second) */
        private const timeToLive:uint = 3;

        /** 通知ウィンドウの画面端からの距離 */
        private const gutter:int = 10;

        private var lifeTicks:uint = 0;

        private var lifeTimer:Timer = new Timer(1000);

        public function NotificationManager():void
        {
            if (instance != null)
            {
                throw new IllegalOperationError("Only one NotificationWindowManager instance should be instantiated");
            }

            lifeTimer.addEventListener(TimerEvent.TIMER, lifeTimer_timerHandler);
            lifeTimer.start();
        }

        public static function getInstance():NotificationManager
        {
            if (instance == null)
            {
                instance = new NotificationManager();
            }

            return instance;
        }

        /**
         * 通知ウィンドウを表示する。<br />
         * Goodの通知は画面左側、Badの通知は画面右側にウィンドウが表示される。
         *
         * @param notifyType 通知の種類
         * @param comment 表示するコメント
         * @param userName 表示するユーザー名
         * @param link 関連リンク情報
         */
        public function notify(notifyType:String = NOTIFY_GOOD, comment:String = "", userName:String = "", link:String = null):void
        {
            var window:NotificationWindow = new NotificationWindow(this, notifyType, comment, userName, link);
            var position:Point = findSpotForNotification(window.bounds, notifyType);
            window.timeToLive = timeToLive;
            window.x = position.x;
            window.y = (notifyType != NOTIFY_BAD) ? Screen.mainScreen.bounds.height : 0;

            window.visible = true;
            window.animateY((notifyType != NOTIFY_BAD) ? position.y : position.y);
        }

        /**
         * 通知が消えないようにタイマーを一時停止する
         */
        public function pauseExpiration():void
        {
            lifeTimer.stop();
        }

        /**
         * 通知が消えないように一時停止しているタイマーを再開する
         */
        public function resumeExpiration():void
        {
            lifeTimer.start();
        }

        private function lifeTimer_timerHandler(event:Event):void
        {
            lifeTicks++;
            var tickEvent:Event = new Event(LIFE_TICK_EVENT, false, false);
            dispatchEvent(tickEvent);
        }

        /**
         * 通知の表示位置を探す
         *
         * @param size 通知サイズ
         * @param notifyType 通知タイプ(Good, Bad, Comment)
         * @return 通知表示位置
         */
        private function findSpotForNotification(size:Rectangle, notifyType:String = NOTIFY_GOOD):Point
        {
            var spot:Point = null;

            if (notifyType == NotificationManager.NOTIFY_GOOD)
            {
                spot = findSpotForGood(size);
            }
            else if (notifyType == NotificationManager.NOTIFY_BAD)
            {
                spot = findSpotForBad(size);
            }
//            else
//            {
//                spot = findSpotForComment(size);
//            }

            return spot;
        }

        private function findSpotForGood(size:Rectangle):Point
        {
            var spot:Point = new Point();

            var startX:int = gutter;
            var startY:int = Screen.mainScreen.bounds.height - size.height - gutter;

            var endX:int = Screen.mainScreen.bounds.width - size.width - gutter;

            for (var x:int = startX; x < endX; x += (size.width + gutter))
            {
                for (var y:int = startY; y > 0; y -= (size.height + gutter))
                {
                    var testRect:Rectangle = new Rectangle(x, y, size.width + gutter, size.height + gutter);
                    if (!isOccupied(testRect))
                    {
                        spot.x = x;
                        spot.y = y;
                        return spot;
                    }
                }
            }

            return spot;
        }

        private function findSpotForBad(size:Rectangle):Point
        {
            var spot:Point = new Point();

            var startX:int = Screen.mainScreen.bounds.width - size.width - gutter;
            var startY:int = size.height + gutter;

            var endY:int = Screen.mainScreen.bounds.height - size.height - gutter;

            for (var x:int = startX; x > 0; x -= (size.width + gutter))
            {
                for (var y:int = startY; y < endY; y += (size.height + gutter))
                {
                    var testRect:Rectangle = new Rectangle(x, y, size.width + gutter, size.height + gutter);
                    if (!isOccupied(testRect))
                    {
                        spot.x = x;
                        spot.y = y;
                        return spot;
                    }
                }
            }

            return spot;
        }

//		へぇ、ぶぅに載せてコメントを流すので、コメント単体のは使用しない
//        private function findSpotForComment(size:Rectangle):Point
//        {
//            var spot:Point = new Point();
//
//            var startX:int = Screen.mainScreen.bounds.width - size.width - gutter;
//            var startY:int = Screen.mainScreen.bounds.height - size.height - gutter;
//
//            for (var x:int = startX; x > 0; x -= (size.width + gutter))
//            {
//                for (var y:int = startY; y > 0; y -= (size.height + gutter))
//                {
//                    var testRect:Rectangle = new Rectangle(x, y, size.width + gutter, size.height + gutter);
//                    if (!isOccupied(testRect))
//                    {
//                        spot.x = x;
//                        spot.y = y;
//                        return spot;
//                    }
//                }
//            }
//
//            return spot;
//        }

        /**
         * 描画領域が他の通知と被っているかどうか調べる
         *
         * @param testRect 通知描画予定領域
         * @return 他の通知と被ってる場合、trueを返す
         */
        private function isOccupied(testRect:Rectangle):Boolean
        {
            var occupied:Boolean = false;
            for each (var window:NativeWindow in NativeApplication.nativeApplication.openedWindows)
            {
                occupied = occupied || window.bounds.intersects(testRect);
            }
            return occupied;
        }
    }
}